//  ---------------------------------------------------------------------------
//  Department of Computer Science, University of Hull
//  DirectX 11 framework, Aug 2012
//
//  ---------------------------------------------------------------------------

#include "DxApp.h"
#include <fstream>


DXApp* DXApp::_instance = NULL;
int DXApp::mouseDownX = 0;
int DXApp::mouseDownY = 0;

DXApp::DXApp() : _dxDev(0), _dxImmedDC(0), _swapChain(0), _renderTargetView(0), _depthStencilView(0), start(0), freq(0.0),  _width(1024), _height(1024), _hApp(0), _hWnd(0)
{
	
	_instance = this;
	
}

DXApp::~DXApp() 
{
	try
	{
		if (_dxImmedDC) { _dxImmedDC->ClearState(); _dxImmedDC->Flush(); }
		if (_renderTargetView) _renderTargetView->Release();
		if (_depthStencilView) _depthStencilView->Release();
	
		if (_swapChainDesc.Windowed) _swapChain->Release();
		if (_dxImmedDC) _dxImmedDC->Release();
		if (_dxDev) _dxDev->Release();
		
		_instance = NULL;
	}
	catch (...)
	{
		MessageBoxA(0, "Exception thrown in DXApp destructor", 0, 0);
	}

}

void DXApp::bindFrameBuffer()
{
	float BLACK[] = { 0.0f, 0.0f, 0.0f, 1.0f };
	float WHITE[] = { 1.0f, 1.0f, 1.0f, 1.0f };
	this->setupViewport();
	// Clear the back buffer 
	_dxImmedDC->ClearRenderTargetView(_renderTargetView, (float*)&BLACK);
	_dxImmedDC->ClearDepthStencilView(_depthStencilView, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1, 0);
}

void DXApp::resetStates()
{
	// reset states
	_dxImmedDC->OMSetBlendState(0, 0, 0xffffffff);
	_dxImmedDC->RSSetState(0);
	_dxImmedDC->OMSetDepthStencilState(0, 0);
}


bool DXApp::initialise(HINSTANCE hInstance, int cmdShow) 
{
	_hApp = hInstance;

	// Register our window class
	WNDCLASSEX wcex = { sizeof(WNDCLASSEX), CS_HREDRAW|CS_VREDRAW, MessageProc,
		0L, 0L, _hApp, NULL, NULL, NULL, NULL, L"myWindow", NULL };
	RegisterClassEx(&wcex);

	// Create a window
	RECT rc = { 0, 0, _width, _height };
	AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE);
	_hWnd = CreateWindow(L"myWindow", L"Desert Island", 
		WS_OVERLAPPEDWINDOW, 0, 0,  /*CW_USEDEFAULT, CW_USEDEFAULT, */
		rc.right-rc.left, rc.bottom-rc.top, NULL, NULL, _hApp, NULL);
	if (!_hWnd) {
		MessageBox(NULL, L"Cannot create window", L"Error", MB_OK|MB_ICONERROR);
		return false;
	}

	// Initialize D3D11
	if (FAILED(initialiseDxDevice())) {
		MessageBox(_hWnd, L"Cannot create D3D11 device", L"Error", MB_OK|MB_ICONERROR);
		return false;
	}

	ShowWindow(_hWnd, cmdShow);
	UpdateWindow(_hWnd);
	
	return true;
}



// Create Direct3D device and swap chain
HRESULT DXApp::initialiseDxDevice() {
	// Get window size
	RECT rc;
	GetClientRect(_hWnd, &rc);
	UINT width = rc.right - rc.left;
	UINT height = rc.bottom - rc.top;

	// Create D3D11 device and swap chain
	UINT createDeviceFlags = 0;
#ifdef _DEBUG
	createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif
	ZeroMemory(&_swapChainDesc, sizeof(_swapChainDesc));
	_swapChainDesc.BufferDesc.Width = width;
	_swapChainDesc.BufferDesc.Height = height;
	_swapChainDesc.BufferDesc.RefreshRate.Numerator = 60;
	_swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
	_swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	_swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
	_swapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;

	// No multisampling
	_swapChainDesc.SampleDesc.Count = 1;
	_swapChainDesc.SampleDesc.Quality = 0;

	_swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	_swapChainDesc.BufferCount = 1;
	_swapChainDesc.OutputWindow = _hWnd;
	_swapChainDesc.Windowed = true;
	_swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
	_swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;

	// Try to create a hardware accelerated device
	const D3D_FEATURE_LEVEL featureLevel[] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_0 };
	HRESULT hr = D3D11CreateDeviceAndSwapChain(
		NULL, // Primary graphics adapter
		D3D_DRIVER_TYPE_HARDWARE, // Use graphics hardware
		NULL, // Use hardware rasteriser
		createDeviceFlags, // Debug flags
		featureLevel, // Feature level
		2, // Elements in the feature level
		D3D11_SDK_VERSION, // SDK version
		&_swapChainDesc, // Description of the swap chain
		&_swapChain, // Returns the created swap chain
		&_dxDev, // Returns the created device
		NULL, // Returns feature level
		&_dxImmedDC // Returns the Device Context 
		);

	if (FAILED(hr)) {
		// If failed, try to create a reference device
		D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_REFERENCE, NULL, createDeviceFlags, 
			NULL, 0, D3D11_SDK_VERSION, &_swapChainDesc, &_swapChain, &_dxDev, NULL, &_dxImmedDC);
		
		MessageBox(_hWnd, L"No DX10 hardware acceleration found.\nSwitching to REFERENCE driver (very slow).", L"Warning", MB_OK|MB_ICONWARNING);
	}


	// Create a depth - stencil view
	_depthStencilDesc.Width = width;
	_depthStencilDesc.Height = height;
	_depthStencilDesc.MipLevels = 1;
	_depthStencilDesc.ArraySize = 1;
	_depthStencilDesc.Format = DXGI_FORMAT_D16_UNORM;
	_depthStencilDesc.SampleDesc = _swapChainDesc.SampleDesc;
	_depthStencilDesc.Usage = D3D11_USAGE_DEFAULT;
	_depthStencilDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
	_depthStencilDesc.CPUAccessFlags = 0;
	_depthStencilDesc.MiscFlags = 0;

	createDepthStencilView();

	// Create a render target view
	createRenderTargetView();

	// Bind the views to the output
	_dxImmedDC->OMSetRenderTargets(1, &_renderTargetView, _depthStencilView);

	// Setup viewport
	setupViewport();

	return S_OK;
}

HRESULT DXApp::onResize(int width, int height) {
	if (!_dxDev)
		return S_FALSE;

	// Release render target and depth-stencil view
	ID3D11RenderTargetView *nullRTV = NULL;
	_dxImmedDC->OMSetRenderTargets(1, &nullRTV, NULL);
	
	if (_depthStencilView) {
		_depthStencilView->Release();
		_depthStencilView = NULL;
	}

	if (_renderTargetView) {
		_renderTargetView->Release();
		_renderTargetView = NULL;
	}

	// Resize swap chain
	_swapChainDesc.BufferDesc.Width = width;
	_swapChainDesc.BufferDesc.Height = height;
	if (_swapChain) 
		_swapChain->ResizeBuffers(_swapChainDesc.BufferCount, _swapChainDesc.BufferDesc.Width, 
			_swapChainDesc.BufferDesc.Height, _swapChainDesc.BufferDesc.Format, _swapChainDesc.Flags);

	// Re-create a depth-stencil view
	_depthStencilDesc.Width = _swapChainDesc.BufferDesc.Width;
	_depthStencilDesc.Height = _swapChainDesc.BufferDesc.Height;

	createDepthStencilView();

	// Re-create a render target 
	createRenderTargetView();

	// Bind the views to the output
	_dxImmedDC->OMSetRenderTargets(1, &_renderTargetView, _depthStencilView);

	// Setup viewport
	setupViewport();

	return S_OK;
}

bool DXApp::loadEffect(const char* filename, std::vector<char> &compiledShader) const {
	std::ifstream fin(filename, std::ios::binary);
	if (!fin)
		return false;
	fin.seekg(0, std::ios_base::end);
	int size = (int)fin.tellg();

	compiledShader.resize(size);
	fin.seekg(0, std::ios_base::beg);
	fin.read(&compiledShader[0], size);
	if (!fin)
		return false;
	return true;
}

void DXApp::setupViewport() {
	D3D11_VIEWPORT vp;
	vp.Width = (float)_swapChainDesc.BufferDesc.Width;
	vp.Height = (float)_swapChainDesc.BufferDesc.Height;
	vp.MinDepth = 0.0f;
	vp.MaxDepth = 1.0f;
	vp.TopLeftX = 0;
	vp.TopLeftY = 0;

	_dxImmedDC->RSSetViewports(1, &vp);
}

HRESULT DXApp::createDepthStencilView() {
	ID3D11Texture2D *dsBuffer = NULL;
	_dxDev->CreateTexture2D(&_depthStencilDesc, NULL, &dsBuffer);
	HRESULT hr = _dxDev->CreateDepthStencilView(dsBuffer, NULL, &_depthStencilView);
	dsBuffer->Release();
	
	return hr;
}

HRESULT DXApp::createRenderTargetView() {
	ID3D11Texture2D *backBuffer = NULL;
	_swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&backBuffer);
	HRESULT hr = _dxDev->CreateRenderTargetView(backBuffer, NULL, &_renderTargetView);
	backBuffer->Release();
	
	return hr;
}

LRESULT CALLBACK DXApp::MessageProc(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam) {
	assert (_instance);


	if( TwEventWin(wnd, message, wParam, lParam) ) // send event message to AntTweakBar
        return 0; // event has been handled by AntTweakBar
    // else process the event message
    // ...
	
	int newX, newY;

	switch (message) 
	{

		case WM_SIZE: 
			_instance->onResize(LOWORD(lParam), HIWORD(lParam));
			return 0;

		case WM_CHAR:
		case WM_KEYDOWN:
			if (wParam == VK_ESCAPE) {
				PostQuitMessage(0);
				return 0;
			}
			_instance->onKey(wParam);
			break;

		case WM_DESTROY:
			PostQuitMessage(0);
			return 0;

		case WM_MOUSEWHEEL:
			_instance->onMouseWheel(wParam);
			break;

		case WM_LBUTTONDOWN:
			mouseDownX = LOWORD(lParam);
			mouseDownY = HIWORD(lParam);
			break;

		case WM_LBUTTONUP:
			newX = LOWORD(lParam);
			newY = HIWORD(lParam);
			if (newX != mouseDownX || newY != mouseDownY)
				_instance->onLMouseDrag(mouseDownX, mouseDownY, newX, newY);
			else
				_instance->onLMouseClick(lParam);
			break;

		case WM_RBUTTONDOWN:
			_instance->onRMouseClick(lParam);
			break;

		default:
			return DefWindowProc(wnd, message, wParam, lParam);
	}
	return 0;
}


void DXApp::startTimer()
{
	LARGE_INTEGER li;
	QueryPerformanceFrequency(&li);
	freq = static_cast<double>(li.QuadPart);

	QueryPerformanceCounter(&li);
	start = li.QuadPart;

}
double DXApp::getDeltaTime()
{
	LARGE_INTEGER li;

	QueryPerformanceCounter(&li);
	__int64 current = li.QuadPart;

	double dt = static_cast<double>(current - start) / freq;

	start = current;

	return dt;
}


void DXApp::onMouseWheel(WPARAM wParam)
{

}

void DXApp::onLMouseClick(LPARAM lParam)
{

}


void DXApp::onRMouseClick(LPARAM lParam)
{


}


void DXApp::onLMouseDrag(int oldX, int oldY, int newX, int newY)
{

}




bool DXApp::setVectorVar(ID3DX11Effect* fx, const void* data, const char* var_name)
{
	ID3DX11EffectVectorVariable* fx_var = fx->GetVariableByName(var_name)->AsVector();
	fx_var->SetFloatVector((float*)data);

	return true;
}

bool DXApp::setMatrixVar(ID3DX11Effect* fx, const void* data, const char* var_name)
{
	ID3DX11EffectMatrixVariable* fx_var = fx->GetVariableByName(var_name)->AsMatrix();
	fx_var->SetMatrix((float*)data);

	return true;
}

bool DXApp::setBoolVar(ID3DX11Effect* fx, bool data, const char* var_name)
{
	ID3DX11EffectScalarVariable* fx_var = fx->GetVariableByName(var_name)->AsScalar();
	fx_var->SetBool(data);
	return true;
}

bool DXApp::setSrvVar(ID3DX11Effect* fx, ID3D11ShaderResourceView* srv, const char* var_name)
{
	ID3DX11EffectShaderResourceVariable* fx_var = fx->GetVariableByName(var_name)->AsShaderResource();
	fx_var->SetResource(srv);

	return true;
}

bool DXApp::setFloatVar(ID3DX11Effect* fx, float data, const char* var_name)
{
	ID3DX11EffectScalarVariable* fx_var = fx->GetVariableByName(var_name)->AsScalar();
	fx_var->SetFloat(data);
	return true;
}
	
bool DXApp::setIntVar(ID3DX11Effect* fx, int data, const char* var_name)
{
	ID3DX11EffectScalarVariable* fx_var = fx->GetVariableByName(var_name)->AsScalar();
	fx_var->SetInt(data);
	return true;
}

bool DXApp::setSrvArrayVar(ID3DX11Effect* fx, ID3D11ShaderResourceView** srv, const char* var_name, unsigned int offset, unsigned int count)
{
	ID3DX11EffectShaderResourceVariable* fx_var = fx->GetVariableByName(var_name)->AsShaderResource();
	HRESULT hr = fx_var->SetResource(*srv);

	return true;
}